#include "precomp.h"

#pragma once

#include "Character.h"
#include "Player.h"
#include<algorithm>
#define LOW_HP 30

const int spx[8]={1,-1,0,0,1,1,-1,-1},spy[8]={0,0,1,-1,-1,1,1,-1},spr[8]={1,1,1,1,1.4142135623,1.4142135623,1.4142135623,1.4142135623};


class AI : public Character{
public:
	AI(const char* image_path, CL_GraphicContext *gc, Map *map, char map_sign, Player *player) 
		: Character(image_path, gc, map, map_sign), _player(player)
	{
		CL_Console::write_line("AI HP: %1", _hp);
		CalObstacle();
		CalState();
		_delta_gun_shot=-1;
		nowMode=0;
	}

	void shoot(){
		Character::shoot();

		this->_guns[_gun_number]->set_shooting(false);

		CL_Console::write_line("AI SHOOT");
	}

	void tick(float delta_time){
		Character::tick(delta_time);
		switch(nowMode)//FSM Mode Selection
		{
		case DoNothing:
			if(CheckPlayerInSight())nowMode=Tracing;
			//if(_hp<=LOW_HP)nowMode=DoNothing;
			break;
		case Attacking:
				_sprite->set_angle(_to_player_angle());
				if(!CheckInLine()){nowMode=Tracing;break;}
				if(CheckPlayerInSight())
				{
					if(_delta_gun_shot==-1)
						_delta_gun_shot=100;
					else if((_delta_gun_shot-=delta_time)<0)
					{
						if(_guns[_gun_number]->get_bullets()==0)this->reload();//_guns[_gun_number]->get_clips()//_guns[_gun_number]->get_bullets();
						if(_guns[_gun_number]->get_clips()==0&&_guns[_gun_number]->get_bullets()==0)
						{
							switch_gun(M4A1);
							if(_guns[_gun_number]->get_clips()==0&&_guns[_gun_number]->get_bullets()==0)
								switch_gun(HAND_GUN);
						}
						this->shoot();
						_delta_gun_shot=100;
					}
				}
		case Tracing:
			_player_pre_state=_ai_state=GetNearState(CL_Pointf(_x,_y));
			//if(_hp<=LOW_HP){nowMode=DoNothing;break;}
			if(!CheckPlayerInSight()){nowMode=LongTracing;break;}//in range

		case LongTracing:
			if(!CheckPlayerInSight())nowMode=Tracing;
			if(LongTracing)
			{
				//CL_Console::write_line("%1 %2",_state[_player_pre_state].x,_state[_player_pre_state].y);
				_ai_state=_player_pre_state;
			}

			
			_player_state=GetNearState(CL_Pointf(_player->get_x(),_player->get_y()));
			_select_step=Astar(_ai_state,_player_state,1000);

			_x+=_speed*spx[_select_step]*.5*delta_time;
			_y+=_speed*spy[_select_step]*.5*delta_time;
			_sprite->set_angle(_to_player_angle());
			
			if(CheckInLine())nowMode=Attacking;
			else if(CheckPlayerInSight())nowMode=Tracing;
			else nowMode=LongTracing;
			
			break;
		}
	}

	void draw(){
		Character::draw(_player->get_x(), _player->get_y());

		GameObject::draw(_x - _player->get_x() + GameObject::center_x, _y - _player->get_y() + GameObject::center_y);
	}
private:
	int _select_step,_ai_state,_player_state;
protected:
	enum FSMMode{DoNothing=0,Tracing,LongTracing,Attacking};
	int nowMode,_player_pre_state;
	
	CL_Angle _to_player_angle(){
		float _atan2  = atan2(_player->get_y() - _y,  _player->get_x()-_x  );
		CL_Angle angle =  CL_Angle::from_radians(_atan2);
		
		//CL_Console::write_line("%1 %2 : %3", _x-_player->get_x() , _y-_player->get_y(), angle.to_degrees());

		return angle;
	}

	Player *_player;


	vector<CL_Rect> _obstacle;
	vector<CL_Pointf> _state;
	vector<vector<int>> _link,_linkstep;
	vector<vector<int>> _search_table;
	vector<int> searchX;

	bool CheckPlayerInSight()
	{return fabs(_player->get_x()-this->get_x())<=330&&fabs(_player->get_y()-this->get_y())<=250;}
	double _cross(CL_Pointf _a,CL_Pointf _b){return _a.x*_b.y-_a.y*_b.x;}

	bool CheckInsect(CL_Pointf _a,CL_Pointf _b,CL_Pointf _c,CL_Pointf _d)
	{
		double _s=_cross(CL_Pointf(_a.x-_c.x,_a.y-_c.y),CL_Pointf(_b.x-_a.x,_b.y-_a.y))/_cross(CL_Pointf(_d.x-_c.x,_d.y-_c.y),CL_Pointf(_b.x-_a.x,_b.y-_a.y));
		double _t=_cross(CL_Pointf(_c.x-_a.x,_c.y-_a.y),CL_Pointf(_d.x-_c.x,_d.y-_c.y))/_cross(CL_Pointf(_b.x-_a.x,_b.y-_a.y),CL_Pointf(_d.x-_c.x,_d.y-_c.y));
		return -0.01<=_s&&_s<=1.01&&-0.01<=_t&&_t<=1.01;
	}

	bool CheckInsect(CL_Rect _r,CL_Pointf _p1,CL_Pointf _p2)
	{

		return CheckInsect(_p1,_p2,CL_Pointf(_r.left,_r.top),CL_Pointf(_r.left,_r.bottom))||CheckInsect(_p1,_p2,CL_Pointf(_r.right,_r.top),CL_Pointf(_r.right,_r.bottom))||CheckInsect(_p1,_p2,CL_Pointf(_r.left,_r.top),CL_Pointf(_r.right,_r.top))||CheckInsect(_p1,_p2,CL_Pointf(_r.left,_r.bottom),CL_Pointf(_r.right,_r.bottom));
	}

	void CalObstacle()
	{
		const int _S=_map->get_tile_size();
		_obstacle.clear();
		//boundary
		_obstacle.push_back(CL_Rect( 0   , 0   , 1*_S,26*_S));
		_obstacle.push_back(CL_Rect( 0   , 0   ,23*_S, 1*_S));
		_obstacle.push_back(CL_Rect(22*_S, 0   ,23*_S,26*_S));
		_obstacle.push_back(CL_Rect( 0   ,25*_S,23*_S,26*_S));

		_obstacle.push_back(CL_Rect( 4*_S, 0   , 5*_S, 5*_S));
		_obstacle.push_back(CL_Rect( 4*_S,21*_S, 5*_S,26*_S));
		_obstacle.push_back(CL_Rect(18*_S,21*_S,19*_S,26*_S));
		_obstacle.push_back(CL_Rect(18*_S, 0   ,19*_S, 5*_S));

		_obstacle.push_back(CL_Rect(11*_S, 5*_S,12*_S,21*_S));
		//_obstacle.push_back(CL_Rect(11*_S,17*_S,12*_S,21*_S));
		_obstacle.push_back(CL_Rect( 6*_S, 9*_S,17*_S,17*_S));
	}

	bool CheckInLine()
	{
		int fh1=0;
		for(fh1=0;fh1<_obstacle.size()&&!CheckInsect(_obstacle[fh1],CL_Pointf(_x,_y),CL_Pointf(_player->get_x(),_player->get_y()));fh1++);
		//CL_Console::write_line("%1 %2 %3 %4", _obstacle[fh1].left,_obstacle[fh1].top,_obstacle[fh1].right,_obstacle[fh1].bottom);
		return fh1==_obstacle.size();
	}

	void CalState()
	{
		const int smapleX=10,smapleY=10,spx[8]={1,-1,0,0,1,1,-1,-1},spy[8]={0,0,1,-1,-1,1,1,-1};;
		const float collision_radius=12;
		vector<vector<int>> vvv;
		vvv.clear();
		_search_table.clear();
		searchX.clear();
		_state.clear();
		_link.clear();
		_linkstep.clear();
		for(int _startX=16;_startX<_map->get_width()*_map->get_tile_size();_startX+=smapleX)
		{
			bool new_X=true;
			vvv.push_back(vector<int>(0));
			for(int _startY=16;_startY<_map->get_height()*_map->get_tile_size();_startY+=smapleY)
			{
				bool ck=false;
				int check=0,check2;
				for(;check<_obstacle.size();check++)
					for(check2=0;check2<4;check2++)
						if(_obstacle[check].contains(CL_Pointf(_startX+collision_radius*spx[check2],_startY+collision_radius*spy[check2])))
							ck=true;
				if(ck)
					vvv[vvv.size()-1].push_back(-1);
				else
				{
					if(new_X)
					{
						searchX.push_back(_startX);
						_search_table.push_back(vector<int>());
						new_X=false;
					}
					_search_table.back().push_back(_state.size());
					vvv.back().push_back(_state.size());
					_state.push_back(CL_Pointf(_startX,_startY));
				}
			}
		}
		for(int fh1=0;fh1<vvv.size();fh1++)
		{
			for(int fh2=0;fh2<vvv[fh1].size();fh2++)
			{
				///lead to which dir?? other dir infor??
				if(vvv[fh1][fh2]!=-1)
				{
					while(vvv[fh1][fh2]>=_link.size()){_link.push_back(vector<int>());_linkstep.push_back(vector<int>());}
					for(int fh3=0;fh3<8;fh3++)
						if(0<=fh1+spx[fh3]&&fh1+spx[fh3]<vvv.size()&&0<=fh2+spy[fh3]&&fh2+spy[fh3]<vvv[0].size()&&vvv[fh1+spx[fh3]][fh2+spy[fh3]]!=-1)
						{
							_link[vvv[fh1][fh2]].push_back(vvv[fh1+spx[fh3]][fh2+spy[fh3]]);
							_linkstep[vvv[fh1][fh2]].push_back(fh3);
						}
				}
			}
		}
		CL_Console::write_line("I see You!! %1 times",_state.size());
	}
	
	int MyBsearch(vector<int>& target_vector,float target_val)
	{
		int lower=0,upper=target_vector.size()-1;
		while(lower<upper)
		{
			int mid=(lower+upper)/2;
			if(_state[target_vector[mid]].y<target_val)lower=mid+1;
			else upper=mid;
		}
		if(_state[target_vector[upper]].y<target_val)return target_vector.size();
		while(upper>0&&_state[target_vector[upper-1]].y>target_val)upper--;
		return upper;
	}

	int GetNearState(CL_Pointf pos)
	{
		
		//for(int fh1=0;fh1<searchX.size();fh1++)
		//	CL_Console::write_line("I see You!! %1 times",searchX[fh1]);
		vector<int>::iterator pos_x=upper_bound(searchX.begin(),searchX.end(),(int)pos.x);
		int res=-1;
		if(pos_x!=searchX.end())//upper rowY
		{
			int pos_y=MyBsearch(_search_table[pos_x-searchX.begin()],pos.y);
			if(pos_y!=_search_table[pos_x-searchX.begin()].size())
			{
				
				int fs=_search_table[pos_x-searchX.begin()][pos_y];
				if(!~res||pos.distance(_state[fs])<pos.distance(_state[res])||(pos.distance(_state[fs])==pos.distance(_state[res])&&(fabs(_state[fs].x-pos.x)+fabs(_state[fs].y-pos.y)<fabs(_state[res].x-pos.x)+fabs(_state[res].y-pos.y))))
					res=fs;
			}
			//CL_Console::write_line("XXI see You!! %1 times",_state[_search_table[pos_x-searchX.begin()][pos_y]].y);
			if(pos_y>0)
			{
				int fs=_search_table[pos_x-searchX.begin()][--pos_y];
				if(!~res||pos.distance(_state[fs])<pos.distance(_state[res])||(pos.distance(_state[fs])==pos.distance(_state[res])&&(fabs(_state[fs].x-pos.x)+fabs(_state[fs].y-pos.y)<fabs(_state[res].x-pos.x)+fabs(_state[res].y-pos.y))))
					res=fs;
			}
			//CL_Console::write_line("I see You!! %1 times",res);
		}
		if(pos_x!=searchX.begin())//lower rowY
		{
			pos_x--;
			//CL_Console::write_line("I see You!! %1 times",res);
			int pos_y=MyBsearch(_search_table[pos_x-searchX.begin()],pos.y);
			if(pos_y!=_search_table[pos_x-searchX.begin()].size())
			{
				int fs=_search_table[pos_x-searchX.begin()][pos_y];
				if(!~res||pos.distance(_state[fs])<pos.distance(_state[res])||(pos.distance(_state[fs])==pos.distance(_state[res])&&(fabs(_state[fs].x-pos.x)+fabs(_state[fs].y-pos.y)<fabs(_state[res].x-pos.x)+fabs(_state[res].y-pos.y))))
					res=fs;
			}
			//CL_Console::write_line("I see You!! %1 times",res);
			if(pos_y>0)
			{
				int fs=_search_table[pos_x-searchX.begin()][--pos_y];
				if(!~res||pos.distance(_state[fs])<pos.distance(_state[res])||(pos.distance(_state[fs])==pos.distance(_state[res])&&(fabs(_state[fs].x-pos.x)+fabs(_state[fs].y-pos.y)<fabs(_state[res].x-pos.x)+fabs(_state[res].y-pos.y))))
					res=fs;
			}
		}
		//CL_Console::write_line("I see You!! %1 times",res);
		return res;
	}
	int _DEST;
	struct Astar_state
	{
		int sno,first_step;
		double w,w2;
		AI* _ai;
		Astar_state(int fs,double fw,AI* _ai,int first_step):sno(fs),w(fw),_ai(_ai),first_step(first_step){w2=w+_ai->_state[sno].distance(_ai->_state[_ai->_DEST]);}
		bool operator<(Astar_state fs)
		{
			return w2>fs.w2;//_ai->_state[sno].distance(_ai->_state[_ai->_DEST])<fs.w+_ai->_state[fs.sno].distance(_ai->_state[_ai->_DEST]);
		}
	};
	vector<Astar_state>heap;
	int Astar(int ini_state,int dest_state,int Layer)//Run till Dest
	{
		_DEST=dest_state;
		heap.clear();
		for(int fh1=0;fh1<_link[ini_state].size();fh1++)
		{
			heap.push_back(Astar_state(_link[ini_state][fh1],_state[ini_state].distance(_state[_link[ini_state][fh1]]),this,_linkstep[ini_state][fh1]));
			push_heap(heap.begin(),heap.end());
		}
		bool mp[3501]={};
		//map<int,bool> mp;
		//mp.clear();
		Astar_state fnt(heap[0]);
		while(!heap.empty()&&Layer--)
		{
			fnt=heap[0];
			pop_heap(heap.begin(),heap.end());
			heap.pop_back();
			if(fnt.sno==dest_state)break;
			//if(mp.find(fnt.sno)==mp.end())
			if(!mp[fnt.sno])
			{
				mp[fnt.sno]=true;
				for(int fh1=0;fh1<_link[fnt.sno].size();fh1++)
					//if(mp.find(_link[fnt.sno][fh1])==mp.end())
					if(!mp[_link[fnt.sno][fh1]])
					{
						heap.push_back(Astar_state(_link[fnt.sno][fh1],fnt.w+_state[fnt.sno].distance(_state[_link[fnt.sno][fh1]]),this,fnt.first_step));
						push_heap(heap.begin(),heap.end());
					}
			}
		}
		return fnt.first_step;
	}
	float _delta_gun_shot;
};